#include "leam-lom-stream.hh"
#include "leam-lom-enums.hh"
#include "leam-lom-errors.hh"
#include "lom-context.hh"
#include "lom-value-factory.hh"
#include "lom-instance-factory.hh"
#include "lom-transaction-events.hh"
#include "leam-stream.hh"
#include <iostream>

using namespace licada;
using namespace leam;
using namespace lom;
using namespace std;
using namespace boost;

namespace leam_lom
{
    LeamLomStream::LeamLomStream() : context( NULL ), leamStream(NULL) 
    {
    }

    void LeamLomStream::setup( ILomContext* _context
			       , LeamStream* _leamStream ) 
    {
	context = _context;
	leamStream = _leamStream;
	binOut.setup( &dataVector );
	outStream.setup( &binOut,
			 &outgoingMap );
	context->getTransactionSystem()->addHandler( this );
	tempClass.initialize( "", context->getValueFactory() );
	tempInstance.setFactory( context->getValueFactory() );
    }

    void LeamLomStream::reset() {
	if ( context )
	    context->getTransactionSystem()->removeHandler( this );
	context = NULL;
	leamStream = NULL;
	dataVector.clear();
    }

    inline void sendClassPair( const pair< const TNameStr, LomClass >& pair, LomBinaryOutStream& outStream )
    {
	outStream << (uint8)ELeamLomMessageTypeClass;
	outStream << pair.second;
    }

    void LeamLomStream::synchronizeClasses() {
	const TNameToClassMap& clsMap = context->getClassManager()->getClassMap();
	do_all( clsMap, bind( sendClassPair, _1, ref(outStream) ) );
    }

    inline void sendInstancePair( const pair< const uint32
				  , LomRoot* >& pair
				  , LomBinaryOutStream& outStream )
    {
	const LomInstance* lomInst = dynamic_cast< const LomInstance* >( pair.second );
	if ( lomInst != NULL )
	{
	    outStream << (uint8)ELeamLomMessageTypeInstance;
	    outStream << pair.first;
	    outStream << *lomInst;
	}
    }
    
    void LeamLomStream::synchronizedInstances() {
	do_all( context->getInstanceManager()->getHandleToRoots()
		, bind(sendInstancePair, _1, ref(outStream)));
    }
    
    void LeamLomStream::flush() {
	if ( dataVector.size() )
	{
	    TBeginMessageReturn ret = leamStream->beginSendMessage( dataVector.size() );
	    *ret.second << dataVector;
	    dataVector.clear();
	}
    }

	
    /**===========================
       ILomTransactionHandler Implementation.
    */
    void LeamLomStream::beginTransactionEvents() {
    }
    
    void LeamLomStream::handle( const ILomTransactionEvent* _evt ) {
	switch( _evt->getEventType() )
	{
	case ETransactionEventTypeEvent:
	{
	    const LomTransactionEventEvent* evt = 
		dynamic_cast< const LomTransactionEventEvent* >(_evt);
	    outStream << (uint8)ELeamLomMessageTypeEvent;
	    outStream << evt->getEventName().c_str();
	    outStream << evt->getEventArgs();
	}
	break;
	case ETransactionEventTypeClassCreated:
	{
	    const LomTransactionClassCreatedEvent* evt = 
		dynamic_cast< const LomTransactionClassCreatedEvent* >(_evt);
	    outStream << (uint8)ELeamLomMessageTypeClass;
	    outStream << *evt->getClass();
	}
	break;
	case ETransactionEventTypeInstanceCreated:
	{
	    const LomTransactionInstanceCreatedEvent* evt = 
		dynamic_cast< const LomTransactionInstanceCreatedEvent* >(_evt);
	    outStream << (uint8)ELeamLomMessageTypeInstance;
	    outStream << evt->getHandle();
	    LomInstance* instance = context->getInstanceManager()->getInstance( evt->getHandle() );
	    outStream << *instance;
	}
	break;
	case ETransactionEventTypeInstanceDestroyed:
	{
	    const LomTransactionInstanceDestroyedEvent* evt = 
		dynamic_cast< const LomTransactionInstanceDestroyedEvent* >(_evt);
	    outStream << (uint8)ELeamLomMessageTypeInstanceDestroyed;
	    outStream << evt->getHandle();
	}
	break;
	case ETransactionEventTypeInstanceProperty:
	{
	    const LomTransactionPropertyEvent* evt = 
		dynamic_cast< const LomTransactionPropertyEvent* >(_evt);
	    outStream << (uint8)ELeamLomMessageTypeInstanceProperty;
	    outStream << evt->getHandle();
	    outStream << (uint32)evt->getPropIndex();
	    outStream << evt->getValue();
	}
	break;
	case ETransactionEventTypeRootObject:
	{
	    const LomTransactionRootObjectEvent* evt = 
		dynamic_cast< const LomTransactionRootObjectEvent* >(_evt);
	    outStream << (uint8)ELeamLomMessageTypeRootObject;
	    outStream << evt->getName().c_str();
	    outStream << evt->getValue();
	}
	break;
	default:
	    throw UnrecognizedTransactionEventType( _evt->getEventType() );
	}
    }
    void LeamLomStream::endTransactionEvents()
    {
	flush();
    }
    
    static inline void sendHandleMapped( THandle reserveHdl, THandle streamHdl, LomBinaryOutStream& outStream )
    {
	outStream << (uint8)ELeamLomMessageTypeHandleMap;
	outStream << reserveHdl;
	outStream << streamHdl;
    }

    /**=================================
       Incoming handle map implementation
    */
    THandle LeamLomStream::streamToWorld( THandle streamHandle )
    {
	THandleHandleMap::iterator find = streamWorldMap.find( streamHandle );
	if ( find == streamWorldMap.end() ) {
	    THandle reserveHandle = context->getInstanceFactory()->reserveHandle();
	    find = streamWorldMap.insert( std::make_pair( streamHandle, reserveHandle ) ).first;
	    sendHandleMapped( reserveHandle, streamHandle, outStream );
	}
	return find->second;
    }

    /**================================
       LeamHandler implementation
    */
    void LeamLomStream::handleData( leam::TMessageId msg
				    , leam::LeamDataInStream& data )
    {
	if ( data.getRemaining() == 0 )
	    return;
	inStream.setup( &data, context->getValueFactory()
			, context->getClassManager()
			, this );
	THandle tempHandle;
	THandle newHandle;
	uint32 tempIndex;
	LomValue* tempValue;
	//LomTransactionGuard guard( context.getTransactionSystem(), this );
	THandleHandleMap::iterator iter;
	LomInstance* tempInstPtr;
	while( data.getRemaining() )
	{
	    //Apply the changes to the current model.
	    uint8 temp;
	    inStream >> temp;
	    ELeamLomMessageType type = (ELeamLomMessageType)temp;
	    switch( type )
	    {
	    case ELeamLomMessageTypeEvent:
		inStream >> tempStr;
		inStream >> tempVector;
		context->getEventSystem()->event( tempStr.c_str(), tempVector );
		break;
	    case ELeamLomMessageTypeInstance:
		inStream >> tempHandle;
		inStream >> tempInstance;
		newHandle = streamToWorld( tempHandle );
		if ( context->getInstanceFactory()->createInstance( 
			 tempInstance, newHandle ) != newHandle )
		    throw LeamLomErrorAllFuckedUp();
		break;
	    case ELeamLomMessageTypeInstanceDestroyed:
		inStream >> tempHandle;
		iter = streamWorldMap.find( tempHandle );
		if ( iter != streamWorldMap.end() )
		    streamWorldMap.erase( iter );
		break;
	    case ELeamLomMessageTypeClass:
		inStream >> tempClass;
		context->getClassManager()->addClass( tempClass );
		break;
	    case ELeamLomMessageTypeInstanceProperty:
		inStream >> tempHandle;
		inStream >> tempIndex;
		inStream >> tempValue;
		{
		    LomValueAutoPtr guard( tempValue, context->getValueFactory() );
		    newHandle = streamToWorld( tempHandle );
		    tempInstPtr = context->getInstanceManager()->getInstance( 
			newHandle );
		    if ( !tempInstPtr ) {
			throw LeamLomErrorAllFuckedUp();
		    }
		    tempInstPtr->setIdx( tempIndex, *tempValue );
		}
		break;
	    case ELeamLomMessageTypeRootObject:
		inStream >> tempStr;
		inStream >> tempValue;
		{
		    LomValueAutoPtr guard( tempValue, context->getValueFactory() );
		    context->setRootObject( tempStr.c_str(), *tempValue );
		}
		break;
	    case ELeamLomMessageTypeHandleMap:
		inStream >> tempHandle;
		inStream >> newHandle;
		streamWorldMap.insert( std::make_pair( tempHandle, newHandle ) );
		break;
	    default:
		throw LeamLomErrorAllFuckedUp();
	    }
	}
    }
    void LeamLomStream::handleError( leam::TMessageId msg
				     , const std::string& stackTrace
				     , leam::LeamDataInStream& data )
    {
	//What am I supposed to do with this?

	cerr << "Error from other connection." << endl;
	cerr << stackTrace << endl;
    }
    void LeamLomStream::handleAck( leam::TMessageId msg )
    {
    }
    void LeamLomStream::handleFin( leam::TMessageId msg )
    {
    }
    void LeamLomStream::handleClose()
    {
    }
}
